<!--
 * @Description: 5.7 代理数组
 * @Author: YWJ
 * @Date: 2025-03-24 17:14:55
 * @LastEditors: YWJ
-->
<template>
  <div></div>
</template>

<script setup>
import { /*ref, reactive, toRefs, */ onMounted } from 'vue';

const bucket = new WeakMap();
const effectStack = []; // effect 栈 ,始终让 activeEffect 指向栈顶的副作用函数
const ITERATE_KEY = Symbol();
const reactiveMap = new WeakMap(); // 定义一个 Map 实例，存储原始对象到代理对象的映射
// const originMethod = Array.prototype.includes;
const arrayInstrumentations = {
  // includes: function (...args) {
  //   // this 是代理对象，先在代理对象中查找，将结果存储到 res 中
  //   let res = originMethod.apply(this, args);
  //   if (res === false) {
  //     // res 为 false 说明没找到，通过 this.raw 拿到原始数组，再去其中查找并更新 res 值
  //     res = originMethod.apply(this.raw, args);
  //   }
  //   return res;
  // },
};

let shouldTrack = true; //  一个标记变量，代表是否进行追踪。默认值为 true，即允许追踪
let activeEffect = null; // 当前副作用函数

// 深响应
const reactive = obj => {
  // 优先通过原始对象 obj 寻找之前创建的代理对象，如果找到了，直接返回已有的代理对象
  const existionProxy = reactiveMap.get(obj);
  //如果存在，则直接返回已有的代理对象，这样就避免了为同一个原始对象多次创建代理对象的问题
  if (existionProxy) return existionProxy;
  // 否则，创建新的代理对象
  const proxy = createReactive(obj);
  // 存储到 Map 中，从而避免重复创建
  reactiveMap.set(obj, proxy);
  return proxy;
};
// 浅响应
const shallowReactive = obj => {
  const existionProxy = reactiveMap.get(obj);
  if (existionProxy) return existionProxy;
  const proxy = createReactive(obj, true);
  reactiveMap.set(obj, proxy);
  return proxy;
};

// 自己定义的 用于处理响应数组方法拦截
const createReactiveArrayMethodHandlers = () => {
  ['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
    const originMethod = Array.prototype[method];
    arrayInstrumentations[method] = function (...args) {
      // this 是代理对象，先在代理对象中查找，将结果存储到 res 中
      let res = originMethod.apply(this, args);

      if (res === false || res === -1) {
        // res 为 false 说明没找到，通过 this.raw 拿到原始数组，再去其中查找，并更新 res 值
        res = originMethod.apply(this.raw, args);
      }
      // 返回最终结果
      return res;
    };
  });
  ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
    const originMethod = Array.prototype[method];
    arrayInstrumentations[method] = function (...args) {
      shouldTrack = false;
      let res = originMethod.apply(this, args);
      shouldTrack = true;
      return res;
    };
  });
};
// 只读
const readonly = obj => {
  return createReactive(obj, false, true);
};
// 浅只读
const shallowReadonly = obj => {
  return createReactive(obj, true, true);
};
//副作用函数
const effect = (fn, options = {}) => {
  const effectFn = () => {
    // 调用 cleanup 函数完成清除工作
    cleanup(effectFn);
    // 当effectFn执行时，将其设置为当前激活的副作用函数
    activeEffect = effectFn;
    effectStack.push(effectFn);
    // 将 fn 的执行结果存储到 res 中
    const res = fn();
    // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把 activeEffect 还原为之前的值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    // 将 res 作为 effectFn 的返回值
    return res;
  };
  effectFn.options = options;
  // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
  effectFn.deps = [];
  if (!options.lazy) {
    effectFn();
  }

  return effectFn;
};
const createReactive = (obj, isShallow = false, isReadonly = false) => {
  // 自己写的  用于替换书中顺序执行下处理数组 includes、indexOf、lastIndexOf 方法
  if (Object.keys(arrayInstrumentations).length === 0) {
    createReactiveArrayMethodHandlers();
  }
  return new Proxy(obj, {
    // 拦截读取操作
    get(target, key, receiver) {
      if (key === 'raw') {
        return target;
      }
      // 如果操作的目标对象是数组，并且key存在于arrayInstrumentations 上,那么返回定义在 arrayInstrumentations 上的值
      if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
        console.log('hdn');
        return Reflect.get(arrayInstrumentations, key, receiver);
      }
      // 非只读的时候才需要建立响应联系
      // 添加判断，如果 key 的类型是 symbol，则不进行追踪,，无论是使用 for...of 循环，还是调用values 等方法，它们都会读取数组的 Symbol.iterator 属性。不要让副作用函数与这类symbol建立联系
      if (!isReadonly && typeof key !== 'symbol') {
        console.log(123, target, key);
        track(target, key);
      }
      // 得到原始值结果
      const res = Reflect.get(target, key, receiver);
      if (isShallow) {
        return res;
      }
      if (typeof res === 'object' && res !== null) {
        // 如果数据为只读，则调用 readonly 对值进行包装
        return isReadonly ? readonly(res) : reactive(res);
      }
      return res;
    },
    set(target, key, newVal, receiver) {
      // 如果是只读的，则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`);
        return true;
      }
      const oldVal = target[key];
      const type = Array.isArray(target)
        ? Number(key) < target.length
          ? 'SET'
          : 'ADD' // 如果代理目标是数组，则检测被设置的索引值是否小于数组长度， 如果是，则视作 SET 操作，否则是 ADD 操作
        : Object.prototype.hasOwnProperty.call(target, key)
        ? 'SET'
        : 'ADD';
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          trigger(target, key, type, newVal);
        }
      }

      return res;
    },
    ownKeys(target) {
      //ownKeys拦截函数，用于拦截for...in 循环对象
      // 将副作用函数与 ITERATE_KEY 关联
      // track(target, ITERATE_KEY);
      // 如果操作目标 target 是数组，则使用 length 属性作为 key 并建立响应联系
      track(target, Array.isArray(target) ? 'length' : ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
    deleteProperty(target, key) {
      // 如果是只读的，则打印警告信息并返回
      if (isReadonly) {
        console.warn(`属性 ${key} 是只读的`);
        return true;
      }
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);

      if (res && hadKey) {
        trigger(target, key, 'DELETE');
      }

      return res;
    },
    // 省略其他拦截函数
  });
};

// 触发函数
const trigger = (target, key, type, newVal) => {
  const depsMap = bucket.get(target);
  if (!depsMap) return;

  const effects = depsMap.get(key);
  const effectsToRun = new Set();
  effects &&
    effects.forEach(effectFn => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn);
      }
    });

  if (type === 'ADD' || type === 'DELETE') {
    const iterateEffects = depsMap.get(ITERATE_KEY);
    iterateEffects &&
      iterateEffects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn);
        }
      });
  }

  // 当操作类型为 ADD 并且目标对象是数组时，应该取出并执行那些与 length 属性相关联的副作用函数
  if (type === 'ADD' && Array.isArray(target)) {
    // 取出与 length 相关联的副作用函数,为什么是length呢？ 因为当 length=1 arr[1]= bar的时候会修改length所以是通过length间接拦截arr新增操作即arr[1]= bar
    const lengthEffects = depsMap.get('length');
    // 将这些副作用函数添加到 effectsToRun 中，待执行
    lengthEffects &&
      lengthEffects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn);
        }
      });
  }

  // 如果操作目标是数组，并且修改了数组的length属性
  if (Array.isArray(target) && key === 'length') {
    depsMap.forEach((effects, key) => {
      // 对于索引大于或等于新的 length 值的元素，需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
      // 需要找到所有索引值大于或等于新的 length 值的元素，然后把与它们相关联的副作用函数取出并执行。
      if (key >= newVal) {
        effects.forEach(effectFn => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn);
          }
        });
      }
    });
  }

  effectsToRun.forEach(effectFn => {
    if (effectFn.options?.scheduler ?? false) {
      effectFn.options.scheduler(effectFn);
    } else {
      effectFn();
    }
  });
};
// 追踪函数 （收集依赖）
const track = (target, key) => {
  // 没有 activeEffect，直接 return
  if (!activeEffect || !shouldTrack) return;
  let depsMap = bucket.get(target);
  if (!depsMap) {
    bucket.set(target, (depsMap = new Map()));
  }
  let deps = depsMap.get(key);
  if (!deps) {
    depsMap.set(key, (deps = new Set()));
  }
  // 把当前激活的副作用函数添加到依赖集合 deps 中
  deps.add(activeEffect);
  // deps 就是一个与当前副作用函数存在联系的依赖集合
  // 将其添加到 activeEffect.deps 数组中
  activeEffect.deps.push(deps); // 新增
};

const cleanup = effectFn => {
  // 遍历 effectFn.deps 数组
  for (let i = 0; i < effectFn.deps.length; i++) {
    // deps 是依赖集合
    const deps = effectFn.deps[i];
    // 将 effectFn 从依赖集合中移除
    deps.delete(effectFn);
  }
  // 最后需要重置 effectFn.deps 数组
  effectFn.deps.length = 0;
};

onMounted(() => {
  // createReactiveArrayMethodHandlers();

  // 示例1
  // const arr = reactive(["foo"]);
  // effect(() => {
  //   console.log(arr.length);
  // });
  // arr[1] = "bar";
  // console.log("🚀 ~ onMounted ~ arr:", arr);

  // 示例2
  // const arr = reactive(["测试"]);
  // effect(() => {
  //   console.log(arr[0]);
  // });
  // arr.length = 0;
  // console.log("🚀 ~ onMounted ~ arr:", arr);

  // 我自己想的 试试看
  // const arr = reactive(["foo"]);
  // const arr1 = reactive(["测试"]);
  // effect(() => {
  //   console.log("arr.length", arr.length);
  //   console.log("arr1[0]", arr1[0]);
  // });
  // arr[1] = "我累个豆";
  // arr1.length = 0;

  // 示例3 遍历for...of
  // const arr3 = reactive([1, 2, 3, 4, 5]);
  // effect(() => {
  //   for (const val of arr3) {
  //     console.log("示例3:", val);
  //   }
  // });
  // arr3[1] = "bar"; // 能够触发响应
  // arr3.length = 0; // 能够触发响应

  // 示例4 5.7.3 数组的查找方法
  // const obj = {};
  // const arr = reactive([obj]);
  // console.log(arr.includes(arr[0])); // true

  // 示例5
  // const obj = {};
  // const arr = reactive([obj]);
  // console.log('示例5', arr.includes(obj)); // true

  // 示例   push 方法向数组中添加元素时，既会读取数组的 length 属性值，也会设置数组的 length属性值。这会导致两个独立的副作用函数互相影响。
  const arr = reactive([]);
  // 第一个副作用函数
  effect(() => {
    arr.push(1);
  });
  // 第二个副作用函数
  effect(() => {
    arr.push(1);
  });
});
</script>
<style lang="scss" scoped></style>
